package com.carey.blog.listener;

import static com.carey.blog.model.Preference.PREFERENCE;

import java.io.BufferedInputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletRequestEvent;
import javax.servlet.http.HttpSessionEvent;

import org.jabsorb.JSONRPCBridge;
import org.json.JSONObject;

import com.carey.blog.event.EventTypes;
import com.carey.blog.event.baidu.zone.BaiduZoneSender;
import com.carey.blog.event.comment.ArticleCommentReplyNotifier;
import com.carey.blog.event.comment.PageCommentReplyNotifier;
import com.carey.blog.event.ping.AddArticleGoogleBlogSearchPinger;
import com.carey.blog.event.ping.UpdateArticleGoogleBlogSearchPinger;
import com.carey.blog.event.sync.impl.BlogJavaAddArticleProcessor;
import com.carey.blog.event.sync.impl.BlogJavaRemoveArticleProcessor;
import com.carey.blog.event.sync.impl.BlogJavaUpdateArticleProcessor;
import com.carey.blog.event.sync.impl.CSDNBlogAddArticleProcessor;
import com.carey.blog.event.sync.impl.CSDNBlogRemoveArticleProcessor;
import com.carey.blog.event.sync.impl.CSDNBlogUpdateArticleProcessor;
import com.carey.blog.event.sync.impl.CnBlogsAddArticleProcessor;
import com.carey.blog.event.sync.impl.CnBlogsRemoveArticleProcessor;
import com.carey.blog.event.sync.impl.CnBlogsUpdateArticleProcessor;
import com.carey.blog.event.tencent.microblog.TencentMicroblogSender;
import com.carey.blog.framework.latke.Latkes;
import com.carey.blog.framework.latke.RunsOnEnv;
import com.carey.blog.framework.latke.cache.Cache;
import com.carey.blog.framework.latke.cache.CacheFactory;
import com.carey.blog.framework.latke.event.Event;
import com.carey.blog.framework.latke.event.EventManager;
import com.carey.blog.framework.latke.repository.Transaction;
import com.carey.blog.framework.latke.servlet.AbstractServletListener;
import com.carey.blog.jsonrpc.impl.AdminService;
import com.carey.blog.jsonrpc.impl.ArticleService;
import com.carey.blog.jsonrpc.impl.BlogSyncService;
import com.carey.blog.jsonrpc.impl.CommentService;
import com.carey.blog.jsonrpc.impl.FileService;
import com.carey.blog.jsonrpc.impl.LinkService;
import com.carey.blog.jsonrpc.impl.PageService;
import com.carey.blog.jsonrpc.impl.PreferenceService;
import com.carey.blog.jsonrpc.impl.StatisticService;
import com.carey.blog.jsonrpc.impl.TagService;
import com.carey.blog.model.Preference;
import com.carey.blog.repository.PreferenceRepository;
import com.carey.blog.repository.impl.PreferenceGAERepository;
import com.carey.blog.util.Skins;
import com.carey.blog.util.jabsorb.serializer.StatusCodesSerializer;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.images.Image;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.utils.SystemProperty;
import com.google.appengine.api.utils.SystemProperty.Environment.Value;

/**
 * servlet listener.
 * 
 */
public final class CareyBlogServletListener extends AbstractServletListener {

	/**
	 * version.
	 */
	public static final String VERSION = "1.1";
	/**
	 * Logger.
	 */
	private static final Logger LOGGER = Logger
			.getLogger(CareyBlogServletListener.class.getName());
	/**
	 * JSONO print indent factor.
	 */
	public static final int JSON_PRINT_INDENT_FACTOR = 4;
	/**
	 * Maximum captcha row.
	 */
	public static final int MAX_CAPTCHA_ROW = 10;
	/**
	 * Maximum captcha column.
	 */
	public static final int MAX_CAPTCHA_COLUM = 10;
	/**
	 * Width of a captcha character.
	 */
	public static final int WIDTH_CAPTCHA_CHAR = 13;
	/**
	 * Height of a captcha character.
	 */
	public static final int HEIGHT_CAPTCHA_CHAR = 20;
	/**
	 * Captcha &lt;"imageName", Image&gt;. For example &lt;"0/5.png", Image&gt;.
	 */
	public static final Map<String, Image> CAPTCHAS = new HashMap<String, Image>();
	/**
	 * B3log Rhythm address.
	 */
	public static final String B3LOG_RHYTHM_ADDRESS = "http://b3log-rhythm.appspot.com:80";
	/**
	 * English month.
	 */
	public static final Map<String, String> EN_MONTHS = new HashMap<String, String>();
	/**
	 * Enter escape.
	 */
	public static final String ENTER_ESC = "_esc_enter_88250_";

	static {
		EN_MONTHS.put("01", "January");
		EN_MONTHS.put("02", "February");
		EN_MONTHS.put("03", "March");
		EN_MONTHS.put("04", "April");
		EN_MONTHS.put("05", "May");
		EN_MONTHS.put("06", "June");
		EN_MONTHS.put("07", "Jule");
		EN_MONTHS.put("08", "August");
		EN_MONTHS.put("09", "September");
		EN_MONTHS.put("10", "October");
		EN_MONTHS.put("11", "November");
		EN_MONTHS.put("12", "December");
	}

	@Override
	public void contextInitialized(final ServletContextEvent servletContextEvent) {
		Latkes.setRunsOnEnv(RunsOnEnv.GAE);

		final Value gaeEnvValue = SystemProperty.environment.value();
		if (SystemProperty.Environment.Value.Production == gaeEnvValue) {
			LOGGER.info("Carey Blog runs on [production] environment");
		} else {
			LOGGER.info("Carey Blog runs on [development] environment");
		}

		LOGGER.log(Level.INFO,
				"Application[id={0}, version={1}, instanceReplicaId={2}]",
				new Object[] { SystemProperty.applicationId.get(),
						SystemProperty.applicationVersion.get(),
						SystemProperty.instanceReplicaId.get() });

		registerRemoteJSServices();
		registerEventProcessor();

		/* call super context Initialized */
		super.contextInitialized(servletContextEvent);

		final PreferenceRepository preferenceRepository = PreferenceGAERepository
				.getInstance();
		final Transaction transaction = preferenceRepository.beginTransaction();
		try {
			loadPreference();
			transaction.commit();
		} catch (final Exception e) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
		}

		loadCaptchas();
		registerRemoteJSServiceSerializers();
	}

	@Override
	public void contextDestroyed(final ServletContextEvent servletContextEvent) {
		super.contextDestroyed(servletContextEvent);
	}

	@Override
	public void sessionCreated(final HttpSessionEvent httpSessionEvent) {
	}

	@Override
	public void sessionDestroyed(final HttpSessionEvent httpSessionEvent) {
	}

	@Override
	public void requestInitialized(final ServletRequestEvent servletRequestEvent) {
	}

	@Override
	public void requestDestroyed(final ServletRequestEvent servletRequestEvent) {
	}

	/**
	 * Loading preference.
	 */
	private void loadPreference() {
		final PreferenceRepository preferenceRepository = PreferenceGAERepository
				.getInstance();
		JSONObject preference = null;

		try {
			preference = preferenceRepository.get(Preference.PREFERENCE);
			if (null == preference) {
				LOGGER.log(Level.SEVERE,
						"Can't not init default skin, please init Carey Blog first");
				return;
			}

			final Skins skins = Skins.getInstance();
			skins.loadSkins(preference);

			final EventManager eventManager = EventManager.getInstance();

			eventManager.fireEventSynchronously(// for upgrade extensions
					new Event<JSONObject>(EventTypes.PREFERENCE_LOAD,
							preference));

			preferenceRepository.update(PREFERENCE, preference);

			final Cache<String, Object> userPreferenceCache = CacheFactory
					.getCache(PREFERENCE);
			userPreferenceCache.put(PREFERENCE, preference.toString());
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new RuntimeException(e);
		}

		try {
			LOGGER.log(Level.INFO, "Loaded preference[{0}]",
					preference.toString(JSON_PRINT_INDENT_FACTOR));
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
		}

	}

	/**
	 * Loads captchas.
	 */
	private void loadCaptchas() {
		try {
			final URL captchaURL = CareyBlogServletListener.class
					.getClassLoader().getResource("captcha.zip");

			final ZipFile zipFile = new ZipFile(captchaURL.getFile());
			final Set<String> imageNames = new HashSet<String>();
			for (int row = 0; row < MAX_CAPTCHA_ROW; row++) {
				for (int column = 0; column < MAX_CAPTCHA_COLUM; column++) {
					imageNames.add(row + "/" + column + ".png");
				}
			}

			final Iterator<String> i = imageNames.iterator();
			while (i.hasNext()) {
				final String imageName = i.next();
				final ZipEntry zipEntry = zipFile.getEntry(imageName);

				final BufferedInputStream bufferedInputStream = new BufferedInputStream(
						zipFile.getInputStream(zipEntry));
				final byte[] captchaCharData = new byte[bufferedInputStream
						.available()];
				bufferedInputStream.read(captchaCharData);
				bufferedInputStream.close();

				final Image captchaChar = ImagesServiceFactory
						.makeImage(captchaCharData);

				CAPTCHAS.put(imageName, captchaChar);
			}

			zipFile.close();
		} catch (final Exception e) {
			LOGGER.severe("Can not load captchs!");
			throw new RuntimeException(e);
		}
	}

	/**
	 * Registers remote JavaScript service serializers.
	 */
	private void registerRemoteJSServiceSerializers() {
		final JSONRPCBridge jsonRpcBridge = JSONRPCBridge.getGlobalBridge();

		try {
			jsonRpcBridge.registerSerializer(new StatusCodesSerializer());
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new RuntimeException(e);
		}
	}

	/**
	 * Determines Carey Blog had been initialized.
	 * 
	 * @return {@code true} if it had been initialized, {@code false} otherwise
	 */
	// XXX: to find a better way?
	public static boolean isInited() {
		try {
			final DatastoreService datastoreService = DatastoreServiceFactory
					.getDatastoreService();
			final Key parentKey = KeyFactory.createKey("parentKind",
					"parentKeyName");
			final Key key = KeyFactory.createKey(parentKey,
					Preference.PREFERENCE, Preference.PREFERENCE);
			datastoreService.get(key);
			return true;
		} catch (final EntityNotFoundException e) {
			LOGGER.log(Level.WARNING, "Carey Blog has not been initialized");
			return false;
		}
	}

	/**
	 * Register event processors.
	 */
	private void registerEventProcessor() {
		try {
			final EventManager eventManager = EventManager.getInstance();

			// new ActivityCreator(eventManager);
			new BaiduZoneSender(eventManager); // baidu zone sender(add article)
			new TencentMicroblogSender(eventManager);
			new ArticleCommentReplyNotifier(eventManager);
			new PageCommentReplyNotifier(eventManager);
			new AddArticleGoogleBlogSearchPinger(eventManager);
			new UpdateArticleGoogleBlogSearchPinger(eventManager);
			new BlogJavaAddArticleProcessor(eventManager);
			new BlogJavaRemoveArticleProcessor(eventManager);
			new BlogJavaUpdateArticleProcessor(eventManager);
			new CSDNBlogAddArticleProcessor(eventManager);
			new CSDNBlogRemoveArticleProcessor(eventManager);
			new CSDNBlogUpdateArticleProcessor(eventManager);
			new CnBlogsAddArticleProcessor(eventManager);
			new CnBlogsRemoveArticleProcessor(eventManager);
			new CnBlogsUpdateArticleProcessor(eventManager);
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, "Register event processors error", e);
			throw new RuntimeException(e);
		}
	}

	/**
	 * Registers remote JavaScript services.
	 */
	private void registerRemoteJSServices() {
		try {
			final AdminService adminService = AdminService.getInstance();
			JSONRPCBridge.getGlobalBridge().registerObject(
					adminService.getServiceObjectName(), adminService);

			final ArticleService articleService = ArticleService.getInstance();
			JSONRPCBridge.getGlobalBridge().registerObject(
					articleService.getServiceObjectName(), articleService);

			final BlogSyncService blogSyncService = BlogSyncService
					.getInstance();
			JSONRPCBridge.getGlobalBridge().registerObject(
					blogSyncService.getServiceObjectName(), blogSyncService);

			final CommentService commentService = CommentService.getInstance();
			JSONRPCBridge.getGlobalBridge().registerObject(
					commentService.getServiceObjectName(), commentService);

			final FileService fileService = FileService.getInstance();
			JSONRPCBridge.getGlobalBridge().registerObject(
					fileService.getServiceObjectName(), fileService);

			final LinkService linkService = LinkService.getInstance();
			JSONRPCBridge.getGlobalBridge().registerObject(
					linkService.getServiceObjectName(), linkService);

			final PageService pageService = PageService.getInstance();
			JSONRPCBridge.getGlobalBridge().registerObject(
					pageService.getServiceObjectName(), pageService);

			final PreferenceService preferenceService = PreferenceService
					.getInstance();
			JSONRPCBridge.getGlobalBridge()
					.registerObject(preferenceService.getServiceObjectName(),
							preferenceService);

			final StatisticService statisticService = StatisticService
					.getInstance();
			JSONRPCBridge.getGlobalBridge().registerObject(
					statisticService.getServiceObjectName(), statisticService);

			final TagService tagService = TagService.getInstance();
			JSONRPCBridge.getGlobalBridge().registerObject(
					tagService.getServiceObjectName(), tagService);
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE,
					"Register remote JavaScript service error", e);
			throw new RuntimeException(e);
		}
	}
}
